#include "graphics/opengl/210/backend.h"

#include "system/window.h"

#include "graphics/rendertarget.h"
#include "graphics/viewport.h"

namespace dw {
	namespace graphics {
		namespace details {

void Backend::initilize(const ContextSetup& setup)
{
	m_mainContext = createMainContext(setup);
	m_context = m_mainContext;

	dwAssert(isValid(), "Backend failed to initialize");
}

void Backend::terminate()
{
	dwAssert(isValid(), "Invalid call to terminate");

	setContext(NULL);

	if (::wglDeleteContext(m_mainContext->m_renderContext) == FALSE)
	{
		dwFail("Backend failed to destroy context");
	}

	delete m_mainContext;
	m_mainContext = NULL;

	m_context = NULL;
}

void Backend::setContext(Context* context)
{
	if (m_context != context)
	{
		m_context = context;

		HDC hDc = m_context != NULL ? m_context->m_deviceContext : NULL;
		HGLRC hRc = m_context != NULL ? m_context->m_renderContext : NULL;

		if (::wglMakeCurrent(hDc, hRc) == FALSE)
		{
			dwFail("Backend failed to setContext");
		}
	}
}

void Backend::setRenderTarget(RenderTarget* renderTarget)
{
	dwAssert(isValid(), "Invalid call to setRenderTarget");

	if (m_renderTarget != renderTarget)
	{
		m_renderTarget = renderTarget;
	}
}

void Backend::setViewport(Viewport* viewport)
{
	dwAssert(isValid(), "Invalid call to setViewport");
	dwAssert(m_renderTarget != NULL, "Invalid call to setViewport");

	if (m_viewport != viewport)
	{
		m_viewport = viewport;

		if (m_viewport != NULL)
		{
			glViewport(m_viewport->getPosition().x, m_viewport->getPosition().y,
				m_viewport->getDimension().x, m_viewport->getDimension().y);
		}
	}
}

void Backend::clearRenderTarget()
{
	dwAssert(isValid(), "Invalid call to clearRenderTarget");
	dwAssert(m_renderTarget != NULL, "Invalid call to clearRenderTarget");

	dw::uint32 clear = m_renderTarget->getClearMode();

	if (clear != RenderTarget::NONE)
	{
		GLbitfield clearFlags = 0;

		if ((clear & RenderTarget::COLOR) != 0)
		{
			const Colorf& color(m_renderTarget->getClearColor());
			clearFlags |= GL_COLOR_BUFFER_BIT;

			::glClearColor(color.r, color.g, color.b, color.a);
		}
		if ((clear & RenderTarget::DEPTH) != 0)
		{
			clearFlags |= GL_DEPTH_BUFFER_BIT;
			::glClearDepth(m_renderTarget->getClearDepth());
		}
		if ((clear & RenderTarget::STENCIL) != 0)
		{
			clearFlags |= GL_STENCIL_BUFFER_BIT;
			::glClearStencil(m_renderTarget->getClearStencil());
		}

		::glClear(clearFlags);
	}
}

void Backend::present()
{
	dwAssert(m_context != NULL, "Invalid call to present, no current context");
	dwAssert(m_renderTarget != NULL, "Invalid call to present");

	if (m_context != NULL)
	{
		if (::SwapBuffers(m_context->m_deviceContext) == FALSE)
		{
			dwFail("Failed to present frame buffer");
		}
	}
}

Backend::RenderWindow* Backend::createRenderWindow()
{
	dwAssert(m_context != NULL, "Invalid call to present, no current context");

	RenderWindow* newRenderWindow = new RenderWindow(m_context);

	return newRenderWindow;
}

Backend::Context* Backend::createMainContext(const ContextSetup& setup)
{
	dwAssert(setup.window != NULL, "Invalid parameter: setup.window is null");

	PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),	// size of this pfd 
		1,								// version number 
		PFD_DRAW_TO_WINDOW |			// support window 
		PFD_SUPPORT_OPENGL |			// support OpenGL 
		PFD_DOUBLEBUFFER,				// double buffered 
		PFD_TYPE_RGBA,					// RGBA type 
		setup.window->getBitDepth(),	// color depth 
		0, 0, 0, 0, 0, 0,				// color bits ignored 
		0,								// no alpha buffer 
		0,								// shift bit ignored 
		0,								// no accumulation buffer 
		0, 0, 0, 0,						// accum bits ignored 
		setup.depthBits,				// 16-bit z-buffer 
		setup.stencilBits,				// 16-bits stencil buffer 
		0,								// no auxiliary buffer 
		PFD_MAIN_PLANE,					// main layer 
		0,								// reserved 
		0, 0, 0							// layer masks ignored 
	};

	HDC hDC = setup.window->getDeviceContext();
	dwAssert(hDC != NULL, "Failed to get Device");

	GLuint pixelFormat = ::ChoosePixelFormat(hDC, &pfd);
	dwAssert(pixelFormat != 0, "Failed to get Device");

	PIXELFORMATDESCRIPTOR bestMatch;

	bool describePixelFormatSucceed = ::DescribePixelFormat(hDC, pixelFormat, sizeof(pfd), &bestMatch) != FALSE;
	dwAssert(describePixelFormatSucceed, "Failed to get Pixel format");
	dwAssert(bestMatch.cDepthBits >= pfd.cDepthBits, "Failed to get Pixel format");

	bool setPixelFormatSucceed = ::SetPixelFormat(hDC, pixelFormat, &pfd) != FALSE;
	dwAssert(setPixelFormatSucceed, "Failed to set Pixel format");

	HGLRC glRC = ::wglCreateContext(hDC);
	dwAssert(glRC != NULL, "Failed to create Context");

	bool makeCurrentSucceed = ::wglMakeCurrent(hDC, glRC) != FALSE;
	dwAssert(makeCurrentSucceed, "Failed to make current device");

	if (!m_glewIsInitialized)
	{
		m_glewIsInitialized = glewInit() == GLEW_OK;
		dwAssert(m_glewIsInitialized, "Failed to initialize glew");
	}

	Context* context = new Context(setup);

	context->m_deviceContext = hDC;
	context->m_renderContext = glRC;

	return context;
}

		} // namespace details
	} // namespace graphics
} // namespace dw
